package com.adee.gulimall.common.utils;

import lombok.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.script.DefaultRedisScript;

import java.util.Arrays;
import java.util.concurrent.TimeUnit;

// @Component
@Data
@NoArgsConstructor
@AllArgsConstructor
// @RequiredArgsConstructor 会为所有标注@NonNull的字段生成一个构造方法
@RequiredArgsConstructor
@Slf4j
public class RedisLock {

    private String uuid;

    @NonNull
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 阻塞获取分布式锁
     */
    public void lock(String lockName){
        ValueOperations<String, Object> ops = redisTemplate.opsForValue();
        Boolean absent;
        for (;;) {
            String m = Long.toString(System.currentTimeMillis());
            // 设置过期时间防止由于异常宕机导致的死锁，由于死锁概率较低，因此设置过期时间长一点，3分钟
            absent = ops.setIfAbsent(lockName, m, 180, TimeUnit.SECONDS);
            if(absent) {
                // 获取锁成功
                uuid = m;
                return;
            } else {
                try {
                    TimeUnit.MILLISECONDS.sleep(100);
                } catch (InterruptedException e) {

                }
            }
        }

    }

    /**
     * 释放分布式锁
     * @param lockName
     */
    public void unlock(String lockName){
        /*使用lua脚本来锁*/
        // 定义lua 脚本
        String script =
                "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                "return redis.call('del', KEYS[1]) " +
                "else " +
                "return 0 " +
                "end";
        // 使用redis执行lua执行
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptText(script);
        // 设置一下返回值类型 为Long
        // 因为删除判断的时候，返回的0,给其封装为数据类型。如果不封装那么默认返回String 类型，
        // 那么返回字符串与0 会有发生错误。
        redisScript.setResultType(Long.class);
        // 第一个要是script 脚本 ，第二个需要判断的key，第三个就是key所对应的值。
        Long execute = redisTemplate.execute(redisScript, Arrays.asList(lockName), uuid);
        log.info("redis unlock by lua script, delete [{}]: {} ", lockName, execute);
    }
}
